using System;
using Microsoft.SPOT;
using System.Threading;
using GHIElectronics.NETMF.FEZ;
using System.Runtime.CompilerServices;

namespace Soigeneris
{
    // A delegate type for hooking up change notifications.
    public delegate void KeyEventHandler(object sender, KeyEventArgs e);

    public partial class LCDLiveText
    {
        public enum ScrollDirection { Up, Down, Right, Left };
        enum Flags { none = 0, update = 1, flash = 2, scroll = 4 };

        #region Static Variables

        // visable area of LCD
        const byte LCD_Cols = FEZ_Shields.KeypadLCD.ROW_SIZE;
        const byte LCD_Rows = FEZ_Shields.KeypadLCD.MAX_ROWS;
        const byte VirtualRows = 10; // virtual screen size
        const byte VirtualCols = 40;//
        const int cursorMax = VirtualRows * VirtualCols;

        static byte[] LCD_Data = new byte[cursorMax];
        static byte[] temp = new byte[LCD_Cols];  // holds copy of line for printing to LCD
        static int cursor = 0;             // position of cursor in the virtual screen
        static byte Offset_X = 0;             // offset of view port in X-axis
        static byte Offset_Y = 0;             // offset of view port in Y-axis
        static byte maxColUsed = 0;             // maximum column used
        static byte maxRowUsed = 0;             // maximum row used, used for smart scroll arrows
        static int updateCntr = 0;             // scales update timer for LCD refresh/flash events
        static bool scrollUpDn = false;         // need to know which scroll icons shoudl be shown
        static bool scrollLfRt = false;
        static bool scrollState = false;         // track sroll icon state (on/off)

        static bool flashState = false;         // track flash state (on/off)
        static Flags flags = Flags.none;         // used to track update state

        public event KeyEventHandler KeyEvent;
        static KeyEventArgs keyArg;              // keep a static arg to keep from NEWing up every time
        static FEZ_Shields.KeypadLCD.Keys currentKey;
        static FEZ_Shields.KeypadLCD.Keys lastKey = FEZ_Shields.KeypadLCD.Keys.None; // for key down filter
        static ExtendedTimer updateTimer; // checks to see if LCD requires an update every 100ms

        #endregion

        #region Constructor

        /// <summary>
        /// Used to instantiate a single instance of the class, 
        /// or return an existing instance
        /// </summary>
        private static readonly LCDLiveText instance = new LCDLiveText();

        /// <summary>
        /// Public method to instantiate/get reference to only
        /// instance of this class.
        /// </summary>
        public static LCDLiveText Instance
        {
            get { return instance; }
        }

        /// <summary>
        /// Constructor, must use 'instance' to instantiate.
        /// </summary>
        private LCDLiveText()
        {
            Reset();
            keyArg = new KeyEventArgs(FEZ_Shields.KeypadLCD.Keys.None);
            updateTimer = new ExtendedTimer(new TimerCallback(updateTimerHandler), null, 1000, 100);
        }

        /// <summary>
        /// Resets LCD to initial conditions, 
        /// must reset before using.
        /// </summary>
        private void Reset()
        {
            FEZ_Shields.KeypadLCD.Initialize();
            FEZ_Shields.KeypadLCD.TurnBacklightOn(10);
            Clear();
        }

        #endregion

        #region Event Related Stuff

        /// <summary>
        /// Handles timer tick event, 
        /// looks for key press (fires event on keu up),
        /// and updates LCD if needed
        /// </summary>
        /// <param name="target"></param>
        void updateTimerHandler(object target)
        {
            currentKey = FEZ_Shields.KeypadLCD.GetKey();
            if (currentKey != FEZ_Shields.KeypadLCD.Keys.None
                & lastKey == FEZ_Shields.KeypadLCD.Keys.None)
            {
                lastKey = currentKey;
            }
            else if (currentKey == FEZ_Shields.KeypadLCD.Keys.None
                & lastKey != FEZ_Shields.KeypadLCD.Keys.None)
            {
                keyArg.key = lastKey;
                lastKey = FEZ_Shields.KeypadLCD.Keys.None;
                OnKeyEvent(keyArg);
            }

            // update every fifth timer tick
            if (flags > Flags.none)
            {
                if (updateCntr >= 5)
                {
                    if ((flags & Flags.flash) > 0)
                    {
                        flashState = !flashState;
                        flags |= Flags.update;
                    }
                    if ((flags & Flags.scroll) > 0)
                    {
                        scrollState = !scrollState ^ flashState; // alt scroll & flash
                        flags |= Flags.update;
                    }
                    updateCntr = 0;
                }

                if ((flags & Flags.update) > 0)
                {
                    flags &= (Flags)(0xFF ^ (int)Flags.update);
                    printBuffer();
                }
            }

            updateCntr++;
        }

        /// <summary>
        /// Invokes the KeyEvent; called whenever list changes
        /// </summary>
        /// <param name="e">Key which is pressed</param>
        protected virtual void OnKeyEvent(KeyEventArgs e)
        {
            if (KeyEvent != null)
            {
                KeyEvent(this, e);
            }
        }

        #endregion

        #region public interface

        /// <summary>
        /// Clear entire display area, and screen buffer
        /// </summary>
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void Clear()
        {
            for (int i = 0; i < LCD_Data.Length; i++)
            {
                LCD_Data[i] = 32; // space ;
            }
            cursor = 0;
            Offset_X = 0;
            Offset_Y = 0;
            maxColUsed = 0;
            maxRowUsed = 0;
            flags = Flags.none;
            FEZ_Shields.KeypadLCD.Clear();
        }

        /// <summary>
        /// Prints at current cursor position
        /// </summary>
        /// <param name="str">String to print</param>
        /// Writes to buffer, sets updateFlag, adds CR
        [MethodImpl(MethodImplOptions.Synchronized)]
        public void Print(String str)
        {
            int start = cursor; // catch start > line end
            int length = ((str.Length + start) < cursorMax) ? str.Length : (cursorMax - cursor);
            int end = start + length;
            maxColUsed = (length > maxColUsed) ? (byte)length : maxColUsed;

            for (int i = 0; i < length; i++)
            {
                LCD_Data[cursor] = (byte)str[i];
                cursor++;
            }

            // new line
            cursor = (int)(((cursor - 1) / VirtualCols) + 1) * VirtualCols;
            maxRowUsed++;
            if (cursor >= cursorMax) { cursor = 0; } // roll data off top??

            updateScrollIcon();    //updateFlag = true;
            flags |= Flags.update;

        }

        /// <summary>
        /// Scroll display
        /// </summary>
        /// <param name="direction">Scroll direction</param>
        public void Scroll(ScrollDirection direction)
        {
            switch (direction)
            {
                case ScrollDirection.Up:
                    if (Offset_Y > 0) { Offset_Y--; }
                    break;
                case ScrollDirection.Down:
                    if (Offset_Y < VirtualRows - LCD_Rows) { Offset_Y++; }
                    break;
                case ScrollDirection.Right:
                    if (Offset_X < VirtualCols - LCD_Cols) { Offset_X++; }
                    break;
                case ScrollDirection.Left:
                    if (Offset_X > 0) { Offset_X--; }
                    break;
            }

            updateScrollIcon();
            flags |= Flags.update;
        }

        /// <summary>
        /// Gets/Sets display flash property
        /// </summary>
        public bool Flash
        {
            get
            {
                return ((flags & Flags.flash) > 0);
            }

            set
            {
                if (value)
                {
                    flags |= Flags.flash;  //set
                }
                else
                {
                    flags ^= Flags.flash; //clear
                }
            }
        }


        #endregion

        #region helpers

        /// <summary>
        /// Print the viewable area from buffer to LCD
        /// </summary>
        /// called by updateTimerHandler
        void printBuffer()
        {
            for (byte i = 0; i < LCD_Rows; i++)
            {
                copyBuffer(i);
                FEZ_Shields.KeypadLCD.SetCursor(i, 0);
                FEZ_Shields.KeypadLCD.Print(temp);
            }
        }

        /// <summary>
        /// Helper function to copy one line from buffer to a screen
        /// sized array sent to LCD print routine
        /// </summary>
        /// <param name="row">Physical LCD row</param>
        [MethodImpl(MethodImplOptions.Synchronized)]
        void copyBuffer(byte row)
        {
            int start = Offset_X + (Offset_Y + row) * VirtualCols;
            int end = start + FEZ_Shields.KeypadLCD.ROW_SIZE;
            int EOL = start + VirtualCols - Offset_X;

            for (int i = 0; i < LCD_Cols; i++) // should inc to LCD width
            {
                temp[i] = (i < EOL & !flashState) ? LCD_Data[start + i] : (byte)32;
            }
            if (scrollState)
            {
                if (row == 0 & scrollLfRt) { temp[LCD_Cols - 1] = (byte)(1); }
                if (row == 1 & scrollUpDn) { temp[LCD_Cols - 1] = (byte)(2); }
            }
        }

        /// <summary>
        /// Updates the SmartScroll icon
        /// </summary>
        /// Called anytime the data in the LCD_data buffer is changed.
        /// The scroll icon is changed to indicate which directions
        /// can be scrolled to from current screen view.
        void updateScrollIcon()
        {
            bool leftScroll = Offset_X > 0;
            bool rightScroll = maxColUsed > Offset_X + 16;
            bool upScroll = Offset_Y > 0;
            bool downScroll = maxRowUsed > Offset_Y + 2;

            if (leftScroll | rightScroll | upScroll | downScroll)
            {
                flags |= Flags.scroll;
            }
            else
            {
                flags &= (Flags)(0xFF ^ (int)Flags.scroll);
            }

            scrollLfRt = false;
            scrollUpDn = false;

            if (leftScroll | rightScroll)
            {
                scrollLfRt = true;
                if (leftScroll & rightScroll)
                {
                    FEZ_Shields.KeypadLCD.genChar(1, LCDLiveText.leftRightScroll);
                }
                else if (rightScroll)
                {
                    FEZ_Shields.KeypadLCD.genChar(1, LCDLiveText.rightScroll);
                }
                else
                {
                    FEZ_Shields.KeypadLCD.genChar(1, LCDLiveText.leftScroll);
                }
            }

            if (upScroll | downScroll)
            {
                scrollUpDn = true;
                if (upScroll & downScroll)
                {
                    FEZ_Shields.KeypadLCD.genChar(2, LCDLiveText.upDownScroll);
                }
                else if (downScroll)
                {
                    FEZ_Shields.KeypadLCD.genChar(2, LCDLiveText.downScroll);
                }
                else
                {
                    FEZ_Shields.KeypadLCD.genChar(2, LCDLiveText.upScroll);
                }
            }

        }

        #endregion helpers

    }

    #region KeyEventArgs helper class

    /// <summary>
    /// This classed used to pass KeyPressed Events
    /// </summary>
    public class KeyEventArgs : EventArgs
    {
        public KeyEventArgs(FEZ_Shields.KeypadLCD.Keys key)
        {
            this.key = key;
        }

        public FEZ_Shields.KeypadLCD.Keys key;

    }

    #endregion
}